#ifndef SSHCONNECTION_H
#define SSHCONNECTION_H

#include <QObject>
#include <libssh/libssh.h>
#include <QDebug>
#include <mutex>

/*
    实现了libssh一些基础函数的封装
*/

namespace Ssh {
    /*
        vt200 as VT220/VT240,
        vt300 as VT320/VT340,
        vt400 as VT420, and
        vt500 as VT510/VT520/VT525.
        xterm
        xterm-256color
    */
    class SshChannel;
    class SshSession;

    struct SshLoginKeys {
        std::string user;
        std::string host;
        int port;
        std::string password;
    };

    // channle
    class SshChannel
    {
    public:
        explicit SshChannel(const ssh_channel& channel)
            : mSshChannel(channel)
        {
        }

        ~SshChannel() {
            if (mSshChannel == nullptr) {
                return;
            }
            if (ssh_channel_is_open(mSshChannel)) {
                ssh_channel_close(mSshChannel);
            }
            ssh_channel_free(mSshChannel);
            mSshChannel = nullptr;
        }

        [[nodiscard]] bool IsNull() const {
            return mSshChannel == nullptr;
        }

        [[nodiscard]] bool IsOpen() const {
            return ssh_channel_is_open(mSshChannel);
        }

        [[nodiscard]] bool IsEof() const {
            return ssh_channel_is_eof(mSshChannel);
        }

        [[nodiscard]] bool Open() const {
            return ssh_channel_open_session(mSshChannel) == SSH_OK;
        }

        void Close() const {
            ssh_channel_close(mSshChannel);
        }

        bool Exec(const char* command) const {
            return ssh_channel_request_exec(mSshChannel, command) == SSH_OK;
        }

        int Read(void* buffer, uint32_t length) const {
            return ssh_channel_read(mSshChannel, buffer, length, 0);
        }

        int Read(void* buffer, uint32_t length, int timeout) const {
            return ssh_channel_read_timeout(mSshChannel, buffer, length, 0, timeout);
        }

        int Write(void* buffer, uint32_t length) const {
            return ssh_channel_write(mSshChannel, buffer, length);
        }

        [[nodiscard]] bool RunInteractiveShell(int cols, int rows) const {
            if (IsNull() || !IsOpen()) {
                qDebug() << "IsNull() || !IsOpen()";
                return false;
            }

            if (ssh_channel_request_pty(mSshChannel) != SSH_OK) {
                qDebug() << "ssh_channel_request_pty(mSshChannel) != SSH_OK";
                return false;
            }

            // const char* term = "xterm";
            // if (ssh_channel_request_pty_size(mSshChannel, term, cols, rows) != SSH_OK) {
            //     qDebug() << "ssh_channel_request_pty_size(mSshChannel, term, cols, rows) != SSH_OK";
            //     return false;
            // }

            if (ssh_channel_request_shell(mSshChannel) != SSH_OK) {
                qDebug() << "ssh_channel_request_shell(mSshChannel) != SSH_OK";
                return false;
            }

            return true;
        }

        [[nodiscard]] bool ChangeShellSize(int cols, int rows) const {
            if (IsNull() || !IsOpen()) {
                return false;
            }

            if (ssh_channel_change_pty_size(mSshChannel, cols, rows) != SSH_OK) {
                return false;
            }
            return true;
        }

    private:
        ssh_channel mSshChannel {nullptr};
    };

    // session
    class SshSession
    {
    public:
        explicit SshSession(const std::string& user, const std::string& host, const int port, std::string password)
        {
            mSshSession = ssh_new();
            if (mSshSession == nullptr) {
                return;
            }
            ssh_options_set(mSshSession, SSH_OPTIONS_USER, user.c_str());
            ssh_options_set(mSshSession, SSH_OPTIONS_HOST, host.c_str());
            ssh_options_set(mSshSession, SSH_OPTIONS_PORT, &port);
            mPassword = std::move(password);
        }

        ~SshSession() {
            if (mSshSession != nullptr) {
                ssh_free(mSshSession);
            }
            mSshSession = nullptr;
        }

        bool IsNull() const
        {
            return mSshSession == nullptr;
        }

        void setHost(const char* host) const {
            ssh_options_set(mSshSession, SSH_OPTIONS_HOST, host);
        }

        void setPort(int port) const {
            ssh_options_set(mSshSession, SSH_OPTIONS_PORT, &port);
        }

        void setPort(const char* port) const {
            ssh_options_set(mSshSession, SSH_OPTIONS_PORT_STR, port);
        }

        void setUser(const char* user) const {
            ssh_options_set(mSshSession, SSH_OPTIONS_USER, user);
        }

        [[nodiscard]] bool isConnected() const {
            return ssh_is_connected(mSshSession);
        }

        [[nodiscard]] bool connect() const {
            auto result = ssh_connect(mSshSession);
            if (result != SSH_OK) {
                qCritical("Error connecting to localhost: %s", ssh_get_error(mSshSession));
                return false;
            }

            // 目前只采用password进入
            result = ssh_userauth_password(mSshSession, nullptr, mPassword.c_str());
            if (result != SSH_AUTH_SUCCESS) {
                qCritical("Error authenticating with password: %s", ssh_get_error(mSshSession));
                ssh_disconnect(mSshSession);
                return false;
            }
            return true;
        }

        void disconnect() const {
            ssh_disconnect(mSshSession);
        }

        [[nodiscard]] SshChannel CreateChannel() const {
            return SshChannel(ssh_channel_new(mSshSession));
        }

        const char* GetError() const{
            return ssh_get_error(mSshSession);
        }

    private:
        ssh_session mSshSession {nullptr};
        std::string mPassword {};
    };

    class SshConnection final : public QObject
    {
        Q_OBJECT

    public:
        explicit SshConnection(SshLoginKeys loginKeys, QObject* parent = nullptr);
        ~SshConnection() override;

    public slots:
        void ConnectSsh();
        void SendCommand(const std::string& command);
        void DisconnectSsh();

    signals:
        void SshConnected();
        void SshDisconnected();
        void SshStandardError(std::string error);
        void SshStandardOutput(const std::string& output);
        void SshExecStarted();
        void SshExecFinished();

    private:
        std::mutex mMutex;
        bool mIsConnect = {false};
        bool mIsExecing {false};
        SshLoginKeys mLoginKeys;
        std::string mCommand {};
    };

}

#endif // SSHCONNECTION_H
